<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数据类型</title>
</head>
<body>
    <script>
        /*
            数据类型：
            1. 基本数据类型：string boolean number null undefined 
                基本数据类型是按值进行访问的
            2. 引用数据类型：object
                引用数据类型在栈内存中保存的实际是对象在堆内存中的引用地址，
                通过这个引用地址可以快速查找到保存在堆内存中的对象
        */

        // 1. typeof  
        /*
            弊端： object null array 都会返回Object
        */

        console.log(typeof '123');   // string
        console.log(typeof 123);     // number
        console.log(typeof null);    // object
        console.log(typeof [1,2,3]);   // object
        console.log(typeof {name:'Jack'});   // object


        // 2. instanceof 
        /*
            原理：检测引用数据类型是否存在其原型链上
            只能用于判断引用数据类型
        */

        console.log([1,2,3] instanceof Array); // true

        // 手写instanceof
        function myInstanceOf(left, right) {
            if (typeof left !== 'object' || left === null) {
                return false;
            };

            let proto = left.__proto__;

            while (true) {
                if (proto === null) return false;
                if (proto === right.prototype) return true;

                proto = proto.__proto__;
            };
        };

        console.log(myInstanceOf([1,2,3], Array));  // true


        // 3. constructor
        /*
            1. null undefined 不能判断（undefined和null没有contructor属性）
            2. 不安全 因为constructor可以改变
        */
        const num = 123;
        console.log(num.constructor === Number);  // true

        // 4. Object.prototype.toString.call()
        /*
            在任何值上调用 Object 原生的 toString() 方法，都会返回一个 [object NativeConstructorName] 格式的字符串
            每个类在内部都有一个 [[Class]] 属性，这个属性中就指定了上述字符串中的构造函数名
            但是它不能检测非原生构造函数的构造函数名
            
            toString方法被调用时，会执行以下几个操作步骤：
                1. 获取this指向的那个对象的[[Class]]属性的值（这也就是为什么我们要用call改变this指向的原因）
                    a. toString内部是获取this指向那个对象的[[Class]]属性值的，如果不改变this指向为我们的目标变量，this将永远指向调用toString的prototype
                    b. 很多对象继承的toString方法重写了，为了能调用正确的toString，才间接的使用call/apply方法
                2. 计算出三个字符串 [object   第一步操作结果 ] 连接后的新字符串
                3. 返回2中的结果 [Object className]

            原理：
                1. 对于null 和 undefined 直接返回 [Object Null]   [Object Undefined]
                2. 不为null或者undefined 则将参数转为对象，在做判断，对于原始类型，转为对象的方法即装箱
                3. 转为对象后，取得该对象的[Symbol.toSttringTag]属性值作为tag，如无该属性，或者该属性值不为字符串类型，则依照表取得tag
                    返回[object + tag] 的形式的字符串
        */

        console.log(Object.prototype.toString.call([1,2,3])); // [object Array]
        console.log(Object.prototype.toString([1,2,3]));

    


        // 5. jquery中的$.type
        // 内部原理就是Object.prototype.toString.call()
        // console.log($.type('123'));

        // 6. 手写
        /*
            1. 基本数据类型
            2. 引用数据类型
            3. null
        */

        function getType(target) {
            if (target === null) return 'null';
            if (typeof target !== 'object') {
                return typeof target;
            };

            return Object.prototype.toString.call(target).slice(8, -1).toString();
        };

        console.log('type1', getType(123));
        console.log('type2', getType([1,2,3]));

    </script> 
</body>
</html>